/* Copyright (c) 2015, 2022, Oracle and/or its affiliates. */

/******************************************************************************
 *
 * This software is dual-licensed to you under the Universal Permissive License
 * (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
 * 2.0 as shown at https://www.apache.org/licenses/LICENSE-2.0. You may choose
 * either license.
 *
 * If you elect to accept the software under the Apache License, Version 2.0,
 * the following applies:
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * NAME
 *   63. autoCommit4nestedExecutes.js
 *
 * DESCRIPTION
 *   Nested executes where the 2nd execute fails used to cause an unexpected
 *   commit, even though the autoCommit:false setting is enabled at the
 *   execute() and/or oracledb level. This is github issue 269. It has
 *   been fixed in 1.4.
 *
 *   https://github.com/oracle/node-oracledb/issues/269
 *
 *****************************************************************************/
'use strict';

const oracledb = require('oracledb');
const assert   = require('assert');
const dbConfig = require('./dbconfig.js');

describe('63. autoCommit4nestedExecutes.js', function() {

  const tableName  = "nodb_issue269tab";
  const procName   = "issue269proc";
  let connection;

  before('prepare table and procedure', async function() {

    const sqlCreateTab =
        " BEGIN "
      + "   DECLARE "
      + "     e_table_missing EXCEPTION; "
      + "     PRAGMA EXCEPTION_INIT(e_table_missing, -00942); "
      + "   BEGIN "
      + "     EXECUTE IMMEDIATE ('DROP TABLE " + tableName + " PURGE'); "
      + "   EXCEPTION "
      + "     WHEN e_table_missing "
      + "     THEN NULL; "
      + "   END;  "
      + "   EXECUTE IMMEDIATE (' "
      + "     CREATE TABLE " + tableName + " ( "
      + "       myts timestamp, p_iname VARCHAR2(40), "
      + "       p_short_name VARCHAR2(40), p_comments VARCHAR2(40) "
      + "     ) "
      + "   '); "
      + " END; ";

    const sqlCreateProc =
        " CREATE OR REPLACE PROCEDURE " + procName + "(p_iname IN VARCHAR2, "
      + "   p_short_name IN VARCHAR2, p_comments IN VARCHAR2, p_new_id OUT NUMBER, p_status OUT NUMBER, "
      + "   p_description OUT VARCHAR2) "
      + " AS "
      + " BEGIN "
      + "   p_description := p_iname || ' ' || p_short_name || ' ' || p_comments; "
      + "   p_new_id := 1; "
      + "   p_status := 2; "
      + "   insert into " + tableName + " values (systimestamp, p_iname, p_short_name, p_comments); "
      + " END; ";

    connection = await oracledb.getConnection(dbConfig);
    await connection.execute(sqlCreateTab);

    await connection.execute(sqlCreateProc);
  }); // before

  after('drop table and procedure', async function() {
    await connection.execute("DROP PROCEDURE " + procName);
    await connection.execute("DROP TABLE " + tableName + " PURGE");
    await connection.close();
  }); // after

  it('63.1 nested execute() functions', async function() {

    // sql will be the same for both execute calls
    const procSql = "BEGIN " + procName + "(p_iname=>:p_iname, p_short_name=>:p_short_name, "
                  + " p_comments=>:p_comments, p_new_id=>:p_new_id, p_status=>:p_status, "
                  + " p_description=>:p_description); END;";

    const pool = await oracledb.createPool(dbConfig);
    const conn = await pool.getConnection();

    await conn.execute(
      procSql,
      {
        p_iname: "Test iname",
        p_short_name: "TST",
        p_comments: "Test comments",
        p_new_id: {
          type: oracledb.NUMBER,
          dir: oracledb.BIND_OUT
        },
        p_status: {
          type: oracledb.NUMBER,
          dir: oracledb.BIND_OUT
        },
        p_description: {
          type: oracledb.STRING,
          dir: oracledb.BIND_OUT
        }
      },
      { autoCommit: false });

    const binds = {
      p_iname123: "Test iname", // specify wrong bind parameter name to cause an error
      p_short_name: "TST",
      p_comments: "Test comments",
      p_new_id: {
        type: oracledb.NUMBER,
        dir: oracledb.BIND_OUT
      },
      p_status: {
        type: oracledb.NUMBER,
        dir: oracledb.BIND_OUT
      },
      p_description: {
        type: oracledb.STRING,
        dir: oracledb.BIND_OUT
      }
    };
    const options = {
      autoCommit: false
    };
    await assert.rejects(
      async () => await conn.execute(procSql, binds, options),
      /ORA-01036:|NJS-097:/
    );

    await conn.release();
    await pool.terminate();

    const result = await connection.execute(
      "SELECT count(*) as amount FROM " + tableName,
      [],
      { outFormat: oracledb.OUT_FORMAT_OBJECT });
    assert.strictEqual(result.rows[0].AMOUNT, 0);

  });

});
